package net.wicp.tams.common.grpc.ribbon;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import org.apache.commons.collections.CollectionUtils;

import com.netflix.appinfo.MyDataCenterInstanceConfig;
import com.netflix.client.ClientFactory;
import com.netflix.config.ConfigurationManager;
import com.netflix.discovery.CacheRefreshedEvent;
import com.netflix.discovery.DefaultEurekaClientConfig;
import com.netflix.discovery.DiscoveryManager;
import com.netflix.discovery.StatusChangeEvent;
import com.netflix.discovery.shared.transport.jersey.Jersey1DiscoveryClientOptionalArgs;
import com.netflix.eventbus.impl.EventBusImpl;
import com.netflix.eventbus.spi.EventBus;
import com.netflix.eventbus.spi.InvalidSubscriberException;
import com.netflix.eventbus.spi.Subscribe;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;

import lombok.extern.slf4j.Slf4j;
import net.wicp.tams.common.Conf;
import net.wicp.tams.common.Result;
import net.wicp.tams.common.apiext.IOUtil;
import net.wicp.tams.common.grpc.ribbon.event.ICacheRefreshedCallback;
import net.wicp.tams.common.grpc.ribbon.event.IStatusChangeCallback;

@SuppressWarnings("deprecation")
@Slf4j
public class RibbonClient {
	private static RibbonClient inst = new RibbonClient();

	private final EventBus eventBus;

	private final RoundRobinRule chooseRule = new RoundRobinRule();

	private RibbonClient() {
		Properties ribbonProp = Conf.getPreToProp("common.grpc.ribbon.default", true);
		ConfigurationManager.loadProperties(ribbonProp);
		Jersey1DiscoveryClientOptionalArgs optionalArgs = new Jersey1DiscoveryClientOptionalArgs();
		eventBus = new EventBusImpl();
		optionalArgs.setEventBus(eventBus);
		try {
			DiscoveryManager.getInstance().initComponent(new MyDataCenterInstanceConfig(),
					new DefaultEurekaClientConfig(), optionalArgs);
			log.info("ribbon初始化完成");

		} catch (Exception e) {
			log.error("初始化ribbon客户端出错", e);
		}
	}

	public static RibbonClient getInst() {
		return inst;
	}

	@SuppressWarnings("rawtypes")
	public DynamicServerListLoadBalancer getLb(String clientName) {
		// ClientFactory.getNamedLoadBalancer会缓存结果, 所以不用担心它每次都会向eureka发起查询
		DynamicServerListLoadBalancer lb2 = (DynamicServerListLoadBalancer) ClientFactory
				.getNamedLoadBalancer(clientName);
		return lb2;
	}

	public DiscoveryEnabledServer getServerOne(String clientName) {
		Server selected = chooseRule.choose(getLb(clientName), null);
		if (null == selected) {
			log.error("服务{}没有可用地址", clientName);
			return null;
		} else {
			return (DiscoveryEnabledServer) selected;
		}
	}

	public List<Server> getServerAvailable(String clientName) {
		List<Server> serverList = getLb(clientName).getReachableServers();
		if (CollectionUtils.isEmpty(serverList)) {
			log.error("服务{}没有可用地址", clientName);
			return Collections.emptyList();
		}
		return serverList;
	}

	public void addClient(String path) {
		Properties properties = IOUtil.fileToProperties(path);
		addClient(properties);
	}

	public void addClient(Properties props) {
		ConfigurationManager.loadProperties(props);
		for (Object key : props.keySet()) {
			String keystr = String.valueOf(key);
			if (keystr.endsWith(".ribbon.DeploymentContextBasedVipAddresses")) {
				hasClient.add(keystr.replace(keystr, ".ribbon.DeploymentContextBasedVipAddresses"));
				break;
			}
		}
	}

	private List<String> hasClient = new ArrayList<>();

	public void addClientByAppName(String appName) {
		if (hasClient.contains(appName)) {
			return;
		}
		Properties props = new Properties();
		props.put(String.format("%s.ribbon.DeploymentContextBasedVipAddresses", appName), "S3");
		props.put(String.format("%s.ribbon.NIWSServerListClassName", appName),
				"com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList");
		props.put(String.format("%s.ribbon.ServerListRefreshInterval", appName), "60000");
		props.put(String.format("%s.ribbon.DeploymentContextBasedVipAddresses", appName), appName);
		addClient(props);
		hasClient.add(appName);
	}

	private Object eventFreshListener = null;

	public Result regFreshEvent(ICacheRefreshedCallback callback) {
		if (eventFreshListener != null) {
			eventBus.unregisterSubscriber(eventFreshListener);
		}
		eventFreshListener = new Object() {
			@Subscribe
			public void consume(CacheRefreshedEvent event) {
				callback.callback(event);
			}
		};
		try {
			eventBus.registerSubscriber(eventFreshListener);
			return Result.getSuc();
		} catch (InvalidSubscriberException e) {
			log.error("注册刷新事件失败");
			return Result.getError("注册刷新事件失败,原因:" + e.getMessage());
		}
	}

	private Object eventStatusChangeListener = null;

	public Result regStatusChangeEvent(IStatusChangeCallback callback) {
		if (eventStatusChangeListener != null) {
			eventBus.unregisterSubscriber(eventStatusChangeListener);
		}
		eventStatusChangeListener = new Object() {
			@Subscribe
			public void consume(StatusChangeEvent event) {
				callback.callback(event);
			}
		};
		try {
			eventBus.registerSubscriber(eventStatusChangeListener);
			return Result.getSuc();
		} catch (InvalidSubscriberException e) {
			log.error("注册刷新事件失败");
			return Result.getError("注册刷新事件失败,原因:" + e.getMessage());
		}
	}

}
